fix(hosting): self-contained module CSS imports + cross-module npm-name imports (#253) - #254
Merged
Merged
Conversation
`gen-pages` emitted `@import "#module/<pkg>/styles.css"` into `modules.generated.css`. That specifier only resolves if the host's `vite.config.ts` defines a matching `resolve.alias` — but that file is scaffold output: written into an app once, then owned and edited there. It is versioned independently of these Python packages, so a Python-only bump 0.0.26 -> 0.0.27 broke `vite build` in every app scaffolded earlier, with `Can't resolve '#module/<pkg>/styles.css'` naming a specifier that appears nowhere in the app's own sources. Emit absolute paths instead, exactly as the `@source` lines in the same file already did. The stated objection to filesystem paths (an ugly `../../../.venv/lib/python3.12/site-packages/...`) only ever applied to *relative* ones. `modules.generated.css` is now self-contained and needs no host cooperation at all. Verified on the scaffold's own stack (vite 6.4.3 + @tailwindcss/vite 4.3.3) that an absolute `@import` resolves with no alias configured, and that the alias form fails there with the reporter's exact error; and on the repo's stack (vite 8) for both `vite build` and the dev server, including a module directory entirely outside the project root. The `#module/<pkg>` alias stays in the scaffold template — it costs nothing and removing it would be a second breaking change to a file apps own — but nothing generated depends on it any more. Also: - biome.json: exclude `modules.generated.css` / `modules.assets.json`, matching the existing exclusions for the other two generated files. Absolute paths are long, so whether a line exceeds biome's 100-char lineWidth depended on where the repo happened to live — `make lint` passed or failed by checkout path. - CHANGELOG: move the #152/#155/#156/#173 entries out of [Unreleased] into the releases that actually shipped them (verified with `git tag --contains`). Leaving them there is what led #253 to conclude the #156 fix was unreleased when it has shipped since v0.0.15.
Deploying simple-module-python with
|
| Latest commit: |
1a96865
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://5343f365.simple-module-python.pages.dev |
| Branch Preview URL: | https://worktree-fix-253-module-css.simple-module-python.pages.dev |
…#253) Nothing in Node's own resolution made this work. A wheel-installed module is not an npm workspace member, so it never lands in node_modules at all; a workspace member *is* symlinked, but onto the source-tree module root — one level above the Python package — so subpaths landed somewhere that does not exist. Either way `@simple-module-py/foo/components/x` failed to resolve. `gen-pages` now records each module's `npm_name` in modules.assets.json and the host aliases it onto the module's **Python package directory**. That anchor is forced, not chosen. A wheel ships `site-packages/<pkg>/**` and nothing above it — Hatch force-includes the module-root package.json *into* the package — so the source-tree module root does not survive installation, and the package dir is the only anchor both layouts share. Verified by inspecting a real built wheel. The practical consequence is that the subpath is relative to the package: @simple-module-py/foo/components/Widget ok in both layouts @simple-module-py/foo/foo/components/Widget workspace-only The second shape is what npm's own symlink gives you for a workspace member, which is why hand-rolled aliases against the module root appear to work in a checkout and break once the module is wheel-installed. npm_name discovery skips a parent package.json unless that directory also holds a pyproject.toml, so a wheel-installed module cannot pick up a stray site-packages/package.json and alias itself onto a stranger. Sibling module names are also excluded from optimizeDeps: they resolve to source directories, not to pre-bundlable packages. Verified end-to-end in a real `smpy new` app on the template's vite 6 stack: a workspace module importing a wheel-installed module by package name — the exact shape from the issue — builds and pulls the sibling's component into the chunk. Also verified the new template degrades gracefully against 0.0.27 packages (no npm_name -> no alias, no crash). Structural changes to stay under the 300-line cap, per CLAUDE.md's "split by responsibility": - host/client_app/module-assets.ts — module discovery lifted out of vite.config.ts (which was already at 291 lines), mirroring the existing compress-assets.ts split. - framework/cli/tests/test_module_npm_aliases.py — npm identity tests, with the importable-module factory moved to a tests/conftest.py fixture so both files share it without a cross-file import. Tests: a real-build guard proving a cross-module import resolves (checked that it fails when the alias is removed, so it is not vacuous), plus unit coverage for both install layouts, the pyproject guard, malformed package.json, and npm-name uniqueness.
…them test_module_css_build.py self-skips without node_modules, and the `Python tests` job runs `make install-py` only — so both real-build guards silently skipped in CI (`test_module_css_build.py ss`). They are the only tests that prove module CSS and cross-module npm-name imports resolve through a real Tailwind/Vite build; every unit test around them stays green while the wiring is broken. `JS build (Vite)` already installs Python deps, JS deps, and runs gen-pages + build, so it is the one job with everything they need.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #253 — both problems in it.
1. Module CSS imports broke every pre-0.0.27 host
gen-pagesemitted@import "#module/<pkg>/styles.css"intomodules.generated.css. That specifier resolves only if the host'svite.config.tsdefines a matchingresolve.alias— but that file is scaffold output: written into an app once, then owned and edited there. It's versioned independently of the Python packages, so upgradingsimple_module_*0.0.26 → 0.0.27 brokevite buildin every app scaffolded earlier, on a specifier appearing nowhere in the app's own sources.Fix: emit absolute paths, exactly as the
@sourcelines in the same file already did. The "ugly../../../.venv/...path" objection only ever applied to relative ones.modules.generated.cssis now self-contained and needs no host cooperation.This is suggestion (1) from the issue, and it subsumes (2) and (3): with nothing to resolve, there's no host config to verify and no
doctorcheck to add. No host action required.2. Cross-module TS imports by npm package name
Now supported:
import { X } from '@simple-module-py/pagebuilder/components/blockRegistry'.gen-pagesrecords each module'snpm_nameinmodules.assets.json; the host aliases it onto the module's Python package directory.That anchor is forced, not chosen — I confirmed it by inspecting a real built wheel, which contains
dashboard/**and nothing above it. Hatch force-includes the module-rootpackage.jsoninto the package, so the source-tree module root doesn't survive installation, and the package dir is the only anchor both layouts share:The second shape is what npm's symlink gives you for a workspace member — which is exactly why a hand-rolled alias against the module root appears to work in a checkout and breaks once the module is wheel-installed.
vite.config.tschange in existing hosts — unlike the CSS fix, the import lives in module source rather than a generated file, so it can't be made self-contained. The CHANGELOG carries the diff. New scaffolds get it automatically, and the new template degrades gracefully against 0.0.27 packages (nonpm_name→ no alias → no crash), which I verified.Correcting the issue's diagnosis
The issue attributes this to the template removing package-name aliases and to the #156 fix being unreleased. Both are wrong, and suggestion (4) rests on them:
git diff v0.0.26 v0.0.27on the template is purely additive — no alias block was ever in the framework template.c42d615) has shipped since v0.0.15. Only its CHANGELOG entry was stale, which is how the issue reached the opposite conclusion.So rather than re-landing something that was never removed, this adds the capability properly, with an anchor that works in both install layouts.
Verification
Measured, not assumed:
@import, vite 6.4.3 +@tailwindcss/vite4.3.3, no alias configured@import, same stack, no alias (negative control)resolve.aliasforced to[]styles.css+ unlayered@themesmpy newapp (vite 6): workspace module imports a wheel-installed module by npm namemake lint/make testNew guards:
test_generated_css_is_self_contained(every@importabsolute, non-aliased, exists on disk), a real-build test that a cross-module npm-name import resolves, and unit coverage for both install layouts, thepyproject.tomlguard, malformedpackage.json, and npm-name uniqueness.Also in here
biome.json— excludemodules.generated.css/modules.assets.json, matching the exclusions already there for the other two generated files. Absolute paths are long, so whether a line exceeded biome's 100-charlineWidthdepended on where the repo was checked out:make lintpassed or failed by directory path. Caught by lint, not inspection.CHANGELOG.md— moved the Vite dev-server scan-imports fails to resolve cross-package bare imports from module pages #152 / fix(vite): seed esbuild nodePaths so scan-imports resolves bare imports from module pages #155 / v0.0.13 cross-package fix doesn't cover workspace-member modules + dep-scan still errors #156 / Login success redirects to / (404) instead of /dashboard #173 entries out of[Unreleased]into the releases that actually shipped them (verified withgit tag --contains), and documented the 0.0.27 regression with a manual workaround for anyone pinned there.host/client_app/module-assets.tslifts module discovery out ofvite.config.ts, mirroring the existingcompress-assets.tssplit; npm-identity tests move to their own file with the importable-module factory shared via atests/conftest.pyfixture.